﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using FLib;
using LibraryGuard.Interfaces;
using LibraryGuard.Interfaces.Configuration;

namespace LibraryGuard.Configuration
{
	/// <summary>
	/// Static utilities.
	/// </summary>
	public static class Utilities
	{
		#region Static fields
		private static XmlSerializer Serializer = new XmlSerializer(typeof(Configuration));
		private static Tuple<Type, PropertyInfo>[] GuardProperties = null;
		#endregion

		#region Constructor
		static Utilities()
		{
			var properties =
				typeof(Configuration)
				.GetProperties()
				.Where(m =>
					m.PropertyType.GetInterfaces().Any(i => i == typeof(IGuardConfiguration)) &&
					m.GetCustomAttributes(typeof(GuardAttribute), false).Length > 0);
			var attributes = properties.Select(p => ((GuardAttribute)p.GetCustomAttributes(typeof(GuardAttribute), false)[0]).GuardType);

			GuardProperties = properties.Zip(attributes, (pi, type) => Tuple.Create(type, pi)).ToArray();
		}
		#endregion

		#region Methods
		/// <summary>
		/// Loads confiugration from file.
		/// </summary>
		/// <param name="file"></param>
		/// <returns></returns>
		public static Configuration LoadConfiguration(string file)
		{
			using (FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read))
			{
				var cfg = (Configuration)Serializer.Deserialize(stream);
				CorrectConfiguration(cfg);
				return cfg;
			}
		}

		/// <summary>
		/// Saves empty configuration to file.
		/// </summary>
		/// <param name="file"></param>
		public static void SaveEmpty(string file)
		{
			using (FileStream stream = new FileStream(file, FileMode.Create, FileAccess.Write))
				Serializer.Serialize(stream, new Configuration());
		}

		/// <summary>
		/// Creates guards from configuration.
		/// </summary>
		/// <param name="config"></param>
		/// <returns></returns>
		public static IGuard[] CreateGuards(Configuration config, Action<string, Exception> onInvalidGuard)
		{
			Validate.Debug(() => config, v => v.NotNull());
			Validate.Debug(() => onInvalidGuard, v => v.NotNull());

			List<IGuard> guards = new List<IGuard>();
			foreach (var info in GuardProperties)
			{
				var data = info.Item2.GetValue(config, null) as IGuardConfiguration;
				if (data != null && data.Enabled)
				{
					try
					{
						guards.Add((IGuard)Activator.CreateInstance(info.Item1, (IConfiguration)config, data));
					}
					catch (TargetInvocationException ex)
					{
						onInvalidGuard(info.Item1.Name, ex.InnerException);
					}
					catch (Exception ex)
					{
						onInvalidGuard(info.Item1.Name, ex);
					}
				}
			}
			return guards.ToArray();
		}

		/// <summary>
		/// Checks config correctness.
		/// </summary>
		/// <param name="config"></param>
		/// <returns></returns>
		public static bool CheckCorrectness(Configuration config)
		{
			return
				config.Directories.Count > 0 &&
				config.Extensions.Count > 0;
		}

		/// <summary>
		/// Converts paths to directories to DirectoryInfos.
		/// </summary>
		/// <param name="config"></param>
		/// <returns></returns>
		public static bool ConvertDirectories(Configuration config)
		{
			foreach (var path in config.Directories)
			{
				try
				{
					var dir = new DirectoryInfo(path);
					if (!dir.Exists)
						return false;
					config.DirectoryInfos.Add(dir);
				}
				catch
				{
					return false;
				}
			}
			return true;
		}

		private static void CorrectConfiguration(Configuration config)
		{
			config.Directories.RemoveAll(d => string.IsNullOrWhiteSpace(d));
			config.Extensions.RemoveAll(e => string.IsNullOrWhiteSpace(e));

			for (int i = 0; i < config.Directories.Count; i++)
			{
				config.Directories[i] = config.Directories[i].Replace('/', '\\').Trim();
				if (!config.Directories[i].EndsWith("\\"))
					config.Directories[i] += "\\";
			}

			for (int i = 0; i < config.Extensions.Count; i++)
			{
				config.Extensions[i] = config.Extensions[i].Trim().TrimStart('.');
			}
		}
		#endregion
	}
}
